package com.lcredu.seckill.controller;

import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.lcredu.seckill.config.AccessLimit;
import com.lcredu.seckill.pojo.*;
import com.lcredu.seckill.rabbitmq.SeckillMQSender;
import com.lcredu.seckill.service.OrderService;
import com.lcredu.seckill.service.SeckillGoodsService;
import com.lcredu.seckill.service.SeckillOrderService;
import com.lcredu.seckill.service.UserService;
import com.lcredu.seckill.util.response.RespBean;
import com.lcredu.seckill.util.response.RespBeanEnum;
import com.lcredu.seckill.vo.GoodsVo;
import com.ramostear.captcha.HappyCaptcha;
import com.ramostear.captcha.common.Fonts;
import com.ramostear.captcha.support.CaptchaStyle;
import com.ramostear.captcha.support.CaptchaType;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author 黎长容
 * @date 2025-08-28 18:07
 */
@Controller
@RequestMapping("/seckill")
@Slf4j
public class SeckillController implements InitializingBean {
    @Resource
    private SeckillGoodsService seckillGoodsService;


    @Resource
    private SeckillOrderService seckillOrderService;

    @Resource
    private OrderService orderService;

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private SeckillMQSender seckillMQSender;

    //判断是否有内存
    HashMap<Long, Boolean> entryStockMap = new HashMap<>();


    private static int count = 0;

    /*
        1. 判断是否登录
        2. 判断订单是否库存不够
        3. 判断是否重复购买
        4. 调用秒杀接口
     */
//    @RequestMapping(value = "/doSeckill")
//    public String doSeckill(Model model, User user, @RequestParam("goodsId") Long goodsId) {
//        System.out.println("goodsId=" + goodsId);
//        //===================秒杀 v1.0 start =========================
//        if (user == null) {
//            return "login";
//        }
//        model.addAttribute("user", user);
//        GoodsVo goodsVo = seckillGoodsService.getGoodsVoById(goodsId);
//        if (goodsVo.getStockCount() < 1) {
//            model.addAttribute("errmsg", RespBeanEnum.STOCK_NOT_ENOUGH.getMessage());
//            return "secKillFail";
//        }
//
//        SeckillOrder seckillOrder = seckillOrderService.getOne(new QueryWrapper<SeckillOrder>().eq("user_id", user.getId()).eq("goods_id", goodsId));
//        if (seckillOrder != null) {
//            model.addAttribute("errmsg", RespBeanEnum.REPEAT_SECKILL.getMessage());
//            return "secKillFail";
//        }
//
//        Order order = orderService.seckill(user, goodsVo);
//        model.addAttribute("order", order);
//        model.addAttribute("goods", goodsVo);
//
//
//        //===================秒杀 v1.0 start =========================
//
//        return "orderDetail";
//    }


    //将查询数据库的动作丢掉redis中可以优化查询速度
//    @RequestMapping(value = "/doSeckill")
//    public String doSeckill(Model model, User user, @RequestParam("goodsId") Long goodsId) {
//        System.out.println("goodsId=" + goodsId);
//        //===================秒杀 v2.0 start =========================
//        if (user == null) {
//            return "login";
//        }
//        model.addAttribute("user", user);
//        GoodsVo goodsVo = seckillGoodsService.getGoodsVoById(goodsId);
//        if (goodsVo.getStockCount() < 1) {
//            model.addAttribute("errmsg", RespBeanEnum.STOCK_NOT_ENOUGH.getMessage());
//            return "secKillFail";
//        }
//
////        SeckillOrder seckillOrder = seckillOrderService.getOne(new QueryWrapper<SeckillOrder>().eq("user_id", user.getId()).eq("goods_id", goodsId));
////        if (seckillOrder != null) {
////            model.addAttribute("errmsg", RespBeanEnum.REPEAT_SECKILL.getMessage());
////            return "secKillFail";
////        }
//        //解决重复抢购
//        SeckillOrder seckillOrder = (SeckillOrder) redisTemplate.opsForValue().get("order:" + user.getId() + goodsId);
//        if (seckillOrder != null) {
//            model.addAttribute("errmsg", RespBeanEnum.REPEAT_SECKILL.getMessage());
//            return "secKillFail";
//        }
//
//        Order order = orderService.seckill(user, goodsVo);
//        model.addAttribute("order", order);
//        model.addAttribute("goods", goodsVo);
//
//
//        //===================秒杀 v2.0 start =========================
//
//        return "orderDetail";
//    }


//    //去redis中获取(V3.0)
//    //1.先从redis中进行库存预减操作,减少请求数量
//    //2.在判断复购之后,进行库存预减操作,如果库存<0,则返回错误页
//    //将查询数据库的动作丢掉redis中可以优化查询速度
//    @RequestMapping(value = "/doSeckill")
//    public String doSeckill(Model model, User user, @RequestParam("goodsId") Long goodsId) {
////        System.out.println("goodsId=" + goodsId);
//        //===================秒杀 v3.0 start =========================
//        if (user == null) {
//            return "login";
//        }
//        model.addAttribute("user", user);
//        GoodsVo goodsVo = seckillGoodsService.getGoodsVoById(goodsId);
//        if (goodsVo.getStockCount() < 1) {
//            model.addAttribute("errmsg", RespBeanEnum.STOCK_NOT_ENOUGH.getMessage());
//            return "secKillFail";
//        }
//
////        SeckillOrder seckillOrder = seckillOrderService.getOne(new QueryWrapper<SeckillOrder>().eq("user_id", user.getId()).eq("goods_id", goodsId));
////        if (seckillOrder != null) {
////            model.addAttribute("errmsg", RespBeanEnum.REPEAT_SECKILL.getMessage());
////            return "secKillFail";
////        }
//        //解决重复抢购
//        SeckillOrder seckillOrder = (SeckillOrder) redisTemplate.opsForValue().get("order:" + user.getId() + goodsId);
//        if (seckillOrder != null) {
//            model.addAttribute("errmsg", RespBeanEnum.REPEAT_SECKILL.getMessage());
//            return "secKillFail";
//        }
//
//        Long decrement = redisTemplate.opsForValue().decrement("seckillGoods:" + goodsId);
//        if (decrement < 0) {
//            redisTemplate.opsForValue().increment("seckillGoods:" + goodsId);
//            model.addAttribute("errmsg", RespBeanEnum.REPEAT_SECKILL.getMessage());
//            return "secKillFail";
//        }
//
//        log.info("这是第{}次进行秒杀", ++count);
//
//        Order order = orderService.seckill(user, goodsVo);
//        model.addAttribute("order", order);
//        model.addAttribute("goods", goodsVo);
//
//
//        //===================秒杀 v3.0 start =========================
//
//        return "orderDetail";
//    }


//    //去redis中获取(V4.0)
//    //1.先从redis中进行库存预减操作,减少请求数量
//    //2.在判断复购之后,进行库存预减操作,如果库存<0,则返回错误页
//    //将查询数据库的动作丢掉redis中可以优化查询速度
//    //加入内存标记,定义一个map,每次请求查询的时候就会发现这个商品是否还存在内存
//    //如果内存为0,则返回错误页面
//    @RequestMapping(value = "/doSeckill")
//    public String doSeckill(Model model, User user, @RequestParam("goodsId") Long goodsId) {
//
////        System.out.println("goodsId=" + goodsId);
//        //===================秒杀 v4.0 start =========================
//        if (user == null) {
//            return "login";
//        }
//        model.addAttribute("user", user);
//        GoodsVo goodsVo = seckillGoodsService.getGoodsVoById(goodsId);
//        if (goodsVo.getStockCount() < 1) {
//            model.addAttribute("errmsg", RespBeanEnum.STOCK_NOT_ENOUGH.getMessage());
//            return "secKillFail";
//        }
//
////        SeckillOrder seckillOrder = seckillOrderService.getOne(new QueryWrapper<SeckillOrder>().eq("user_id", user.getId()).eq("goods_id", goodsId));
////        if (seckillOrder != null) {
////            model.addAttribute("errmsg", RespBeanEnum.REPEAT_SECKILL.getMessage());
////            return "secKillFail";
////        }
//        //解决重复抢购
//        SeckillOrder seckillOrder = (SeckillOrder) redisTemplate.opsForValue().get("order:" + user.getId() + goodsId);
//        if (seckillOrder != null) {
//            model.addAttribute("errmsg", RespBeanEnum.REPEAT_SECKILL.getMessage());
//            return "secKillFail";
//        }
//
//        //在这里我们可以直接先判断库存是否为空,从而进行下一步动作
//        if (!entryStockMap.get(goodsId)) {
//            //如果是false,则直接返回错误页面
//            model.addAttribute("errmsg", RespBeanEnum.REPEAT_SECKILL.getMessage());
//            return "secKillFail";
//        }
//
//        Long decrement = redisTemplate.opsForValue().decrement("seckillGoods:" + goodsId);
//        if (decrement < 0) {
//            //如果我库存预减小于0的话,就标记库存为空
//            entryStockMap.put(goodsId, false);
//            redisTemplate.opsForValue().increment("seckillGoods:" + goodsId);
//            model.addAttribute("errmsg", RespBeanEnum.REPEAT_SECKILL.getMessage());
//            return "secKillFail";
//        }
//
//        log.info("这是第{}次进行秒杀", ++count);
//
//        Order order = orderService.seckill(user, goodsVo);
//        model.addAttribute("order", order);
//        model.addAttribute("goods", goodsVo);
//
//
//        //===================秒杀 v3.0 start =========================
//
//        return "orderDetail";
//    }


//    //去redis中获取(V4.0)
//    //1.先从redis中进行库存预减操作,减少请求数量
//    //2.在判断复购之后,进行库存预减操作,如果库存<0,则返回错误页
//    //将查询数据库的动作丢掉redis中可以优化查询速度
//    //加入内存标记,定义一个map,每次请求查询的时候就会发现这个商品是否还存在内存
//    //如果内存为0,则返回错误页面
//    //加入rabbitmq设置秒杀消息到交换机异步请求
//    @RequestMapping(value = "/doSeckill")
//    public String doSeckill(Model model, User user, @RequestParam("goodsId") Long goodsId) {
//
////        System.out.println("goodsId=" + goodsId);
//        //===================秒杀 v4.0 start =========================
//        if (user == null) {
//            return "login";
//        }
//        model.addAttribute("user", user);
//        GoodsVo goodsVo = seckillGoodsService.getGoodsVoById(goodsId);
//        if (goodsVo.getStockCount() < 1) {
//            model.addAttribute("errmsg", RespBeanEnum.STOCK_NOT_ENOUGH.getMessage());
//            return "secKillFail";
//        }
//
////        SeckillOrder seckillOrder = seckillOrderService.getOne(new QueryWrapper<SeckillOrder>().eq("user_id", user.getId()).eq("goods_id", goodsId));
////        if (seckillOrder != null) {
////            model.addAttribute("errmsg", RespBeanEnum.REPEAT_SECKILL.getMessage());
////            return "secKillFail";
////        }
//        //解决重复抢购
//        SeckillOrder seckillOrder = (SeckillOrder) redisTemplate.opsForValue().get("order:" + user.getId() + goodsId);
//        if (seckillOrder != null) {
//            model.addAttribute("errmsg", RespBeanEnum.REPEAT_SECKILL.getMessage());
//            return "secKillFail";
//        }
//
//        //在这里我们可以直接先判断库存是否为空,从而进行下一步动作
//        if (!entryStockMap.get(goodsId)) {
//            //如果是false,则直接返回错误页面
//            model.addAttribute("errmsg", RespBeanEnum.REPEAT_SECKILL.getMessage());
//            return "secKillFail";
//        }
//
//        Long decrement = redisTemplate.opsForValue().decrement("seckillGoods:" + goodsId);
//        if (decrement < 0) {
//            //如果我库存预减小于0的话,就标记库存为空
//            entryStockMap.put(goodsId, false);
//            redisTemplate.opsForValue().increment("seckillGoods:" + goodsId);
//            model.addAttribute("errmsg", RespBeanEnum.REPEAT_SECKILL.getMessage());
//            return "secKillFail";
//        }
//
//        log.info("这是第{}次进行秒杀", ++count);
//
//        //既然是发送秒杀消息就先创建这个消息
//        SeckillMessage seckillMessage = new SeckillMessage(goodsId, user);
//        //转为String
//        String seckillMsg = JSONUtil.toJsonStr(seckillMessage);
//        seckillMQSender.sendMessage(seckillMsg);
//        model.addAttribute("errmsg", "秒杀排队中...请稍等");
//
//        return "secKillFail";
//
//    }

    //去redis中获取(V5.0)
    //1.先从redis中进行库存预减操作,减少请求数量
    //2.在判断复购之后,进行库存预减操作,如果库存<0,则返回错误页
    //将查询数据库的动作丢掉redis中可以优化查询速度
    //加入内存标记,定义一个map,每次请求查询的时候就会发现这个商品是否还存在内存
    //如果内存为0,则返回错误页面
    //加入rabbitmq设置秒杀消息到交换机异步请求
    //5.0 秒杀安全
    @RequestMapping(value = "/{path}/doSeckill")
    @ResponseBody
    public RespBean doSeckill(@PathVariable String path, Model model, User user, @RequestParam("goodsId") Long goodsId, HttpServletRequest request) {

//        System.out.println("goodsId=" + goodsId);
        //===================秒杀 v5.0 start =========================
        if (user == null) {
            return RespBean.error(RespBeanEnum.SESSION_ERROR);
        }
        //检查路径
        boolean b = orderService.checkPath(user, goodsId, path);
        if (!b) {
            return RespBean.error(RespBeanEnum.REQUEST_ILLEGAL);
        }


        GoodsVo goodsVo = seckillGoodsService.getGoodsVoById(goodsId);
        if (goodsVo.getStockCount() < 1) {
            return RespBean.error(RespBeanEnum.STOCK_NOT_ENOUGH);
        }
        //解决重复抢购
        SeckillOrder seckillOrder = (SeckillOrder) redisTemplate.opsForValue().get("order:" + user.getId() + ":" + goodsId);
        if (seckillOrder != null) {
            return RespBean.error(RespBeanEnum.REPEAT_SECKILL);
        }

        //在这里我们可以直接先判断库存是否为空,从而进行下一步动作
        if (!entryStockMap.get(goodsId)) {
            //如果是false,则直接返回错误页面
            return RespBean.error(RespBeanEnum.REPEAT_SECKILL);
        }

        Long decrement = redisTemplate.opsForValue().decrement("seckillGoods:" + goodsId);
        if (decrement < 0) {
            //如果我库存预减小于0的话,就标记库存为空
            entryStockMap.put(goodsId, false);
            redisTemplate.opsForValue().increment("seckillGoods:" + goodsId);
            return RespBean.error(RespBeanEnum.REPEAT_SECKILL);
        }

        log.info("这是第{}次进行秒杀", ++count);

        //既然是发送秒杀消息就先创建这个消息
        SeckillMessage seckillMessage = new SeckillMessage(goodsId, user);
        System.out.println("注意这个消息--SeckillController");
        //转为String
        String seckillMsg = JSONUtil.toJsonStr(seckillMessage);
        seckillMQSender.sendMessage(seckillMsg);

        return RespBean.error(RespBeanEnum.SEC_KILL_WAIT);

    }

    @GetMapping("/path")
    @ResponseBody
    @AccessLimit(second = 10, maxCount = 5, needLogin = true)
    public RespBean getPath(User user, Long goodsId, String captcha, HttpServletRequest request) {
        if (user == null) {
            return RespBean.error(RespBeanEnum.SESSION_ERROR);
        }

        //接口防刷
        /**
         * 1. 获取uri的值
         * 2. 在redis存储uri以及相对应的点击次数(seckill/path:userId)
         * 3. 如果获取到的是null,就设置初始值,过期时间设置为5s
         * 4. 如果有值且<5,则进行加一
         * 5. 如果有值但>5,则给出提示信息
         */
//        String uri = request.getRequestURI();
//        ValueOperations valueOperations = redisTemplate.opsForValue();
//        String key = uri + ":" + user.getId();
//        Integer count = (Integer) valueOperations.get(key);
//        if (count == null) {
//            valueOperations.set(key, 1, 15, TimeUnit.SECONDS);
//        } else if (count < 5) {
//            valueOperations.increment(key);
//        } else {
//            return RespBean.error(RespBeanEnum.REPEAT_CLICK);
//        }


        //校验验证码
        boolean b = orderService.checkCaptcha(user, goodsId, captcha);
        if (!b) {
            return RespBean.error(RespBeanEnum.CAPTCHA_ERROR);
        }
        String url = orderService.createPath(user, goodsId);
        return RespBean.success(url);
    }

    //生成验证码
    //默认已将放入了session且值为happy-captcha
    //放入redis(captcha:userId:goodsId)
    @GetMapping("/captcha")
    public void captcha(User user, Long goodsId, HttpServletRequest req, HttpServletResponse res) {
        HappyCaptcha.require(req, res)
                //设置展现样式为动画
//                .style(CaptchaStyle.ANIM)
                //设置验证码内容为汉字
                .type(CaptchaType.NUMBER)
                //设置字符长度为6
                .length(6)
                //设置动画宽度为220
                .width(220)
                //设置动画高度为80
                .height(80)
                //设置汉字的字体
                .font(Fonts.getInstance().zhFont())
                //生成并输出验证码
                .build().finish();

        redisTemplate.opsForValue().set("captcha:" + user.getId() + ":" + goodsId,
                req.getSession().getAttribute("happy-captcha"),
                100,
                TimeUnit.SECONDS);
    }

    // 该方法会在Spring容器初始化该Bean的所有属性之后自动调用
    // 可用于执行一些初始化逻辑，例如预热缓存、加载配置等
    @Override
    public void afterPropertiesSet() throws Exception {
        log.info("GoodsController 初始化完成，执行 afterPropertiesSet 方法");
        List<SeckillGoods> seckillGoods = seckillGoodsService.list();
        //存入redis 设计存储方式 seckill:goosId  数量
        seckillGoods.forEach(seckillGood -> {
                    redisTemplate.opsForValue().set("seckillGoods:" + seckillGood.getGoodsId(), seckillGood.getStockCount());
                    //在这里进行map初始化
                    entryStockMap.put(seckillGood.getGoodsId(), true);
                }

        );
    }
}
